package com.cy.ppj.service.wechat.impl;

import com.cy.ppj.comm.constant.CacheConstants;
import com.cy.ppj.comm.utils.JsonObjectMapper;
import com.cy.ppj.dao.mongodb.MongoDao;
import com.cy.ppj.dao.mongodb.WechatTemplateLogDao;
import com.cy.ppj.model.Result;
import com.cy.ppj.model.doc.WechatTemplateLog;
import com.cy.ppj.model.domian.user.UserWechat;
import com.cy.ppj.model.dto.wechat.WechatTemplateDTO;
import com.cy.ppj.model.enums.wechat.WechatSopce;
import com.cy.ppj.service.BaseService;
import com.cy.ppj.service.user.UserService;
import com.cy.ppj.service.user.UserWechatService;
import com.cy.ppj.service.wechat.WechatService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @description: ppj微信公众号 微信服务
 * @author: Ocean
 * @create: 2018-08-05 01:13
 **/
@Slf4j
@Service
public class WechatServiceImpl extends BaseService implements WechatService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private UserService userService;
    @Autowired
    private UserWechatService userWechatService;
    @Autowired
    private WechatTemplateLogDao wechatTemplateLogDao;
    @Autowired
    private MongoDao mongoDao;

    @Value("${wecaht.access-token.url}")
    private String tokenUrl;
    @Value("${wechat.ticket.url}")
    private String ticketUrl;
    @Value("${wechat.app-id}")
    private String appId;
    @Value("${wechat.secret}")
    private String secret;
    /**
     * 用户授权地址
     */
    @Value("${wechat.oauth2.authorize.url}")
    private String oauth2AuthorizeUrl;
    /**
     * 获取用户access_token地址
     */
    @Value("${wechat.oauth2.access-token.url}")
    private String oauth2AccessTokenUrl;
    /**
     * 获取用户信息地址
     */
    @Value("${wechat.oauth2.user-info.url}")
    private String userInfoUrl;
    /**
     * 回调地址
     */
    @Value("${wechat.oauth2.bind.callback.url}")
    private String bindCallbackUrl;

    @Value("${wechat.auto-login.callback.url}")
    private String autoLoginCallbackUrl;

    @Value("${wechat.template.send.url}")
    private String templateSendUrl;

    private final String shareKey = "jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s";

    private final String authKey = "WECHAT_AUTH_KEY:";

    /**
     * 获取微信分享access_token
     *
     * @return
     */
    @Override
    @Cacheable(value = CacheConstants.CACHE_VALUE_1H)
    public String getAccessToken() {
        //获取TOKEN
        Map<String, String> map = new HashMap<>();
        map.put("appid", appId);
        map.put("secret", secret);
        ResponseEntity<Map> responseEntity = restTemplate.getForEntity(tokenUrl, Map.class, map);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TOKEN失败");
        map = responseEntity.getBody();
        log.info("请求token回调: {}", map);
        String token = map.get("access_token");
        state(StringUtils.hasLength(token), "获取TOKEN失败");
        return token;
    }

    /**
     * 获取微信分享Ticket
     *
     * @param token
     * @return
     */
    @Override
    @Cacheable(value = CacheConstants.CACHE_VALUE_1H)
    public String getTicket(String token) {
        Map<String, String> map = new HashMap<>();
        map.put("token", token);
        ResponseEntity<Map> responseEntity = restTemplate.getForEntity(ticketUrl, Map.class, map);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TICKET失败");
        map = responseEntity.getBody();
        log.info("请求ticket回调: {}", map);
        String ticket = map.get("ticket");
        state(StringUtils.hasLength(ticket), "获取TICKET失败");
        return ticket;
    }

    /**
     * 获取分享签名
     *
     * @param token
     * @param ticket
     * @param url
     * @return
     */
    @Override
    public Map<String, String> getSignature(String token, String ticket, String url) {
        String noncestr = UUID.randomUUID().toString();
        String timestamp = System.currentTimeMillis() + "";
        timestamp = timestamp.substring(0, 10);
        String sign = String.format(shareKey, ticket, noncestr, timestamp, url);
        String signature = DigestUtils.sha1Hex(sign);
        Map<String, String> map = new HashMap<>();
        map.put("appId", appId);
        map.put("timestamp", timestamp);
        map.put("nonceStr", noncestr);
        map.put("signature", signature);
        map.put("url", url);
        return map;
    }

    /**
     * 获取用户授权路径
     *
     * @param userId
     * @return
     */
    @Override
    public String getAuthorizeUrl(Long userId, WechatSopce sopce) {

        //生成随机key
        String state = UUID.randomUUID().toString();
        String url = String.format(oauth2AuthorizeUrl, appId, bindCallbackUrl, sopce.getSopce(), state);

        String key = authKey.concat(state);

        Map<String, String> map = new HashMap<>();
        map.put("userId", userId.toString());
        map.put("sopce", sopce.name());

        //缓存
        redisTemplate.opsForHash().putAll(key, map);
        redisTemplate.expire(key, 4, TimeUnit.MINUTES);

        return url;
    }

    @Override
    public String getAutoLoginUrl() {

        //生成随机key
        String state = UUID.randomUUID().toString();
        String url = String.format(oauth2AuthorizeUrl, appId, autoLoginCallbackUrl, WechatSopce.BASE.getSopce(), state);

        return url;
    }


    /**
     * 用户授权回调方法
     * 
     * @param code
     * @param state
     * @return
     */
    @Override
    public UserWechat callback(String code, String state) {

        String key = authKey.concat(state);
        //缓存
        Map<String, String> map = redisTemplate.opsForHash().entries(key);
        state(!StringUtils.isEmpty(map), "验证超时，请重新操作");


        Long userId = Long.valueOf(map.get("userId"));
        WechatSopce sopce = WechatSopce.get(map.get("sopce"));

        //获取用户accessToken
        Map<String, Object> body = getUserAccessToken(code);

        String openId = body.get("openid").toString();
        String accessToken = body.get("access_token").toString();

        //获取用户用户信息
        Object unionId = body.get("unionid");
        Object headimgurl = body.get("headimgurl");
        Object nickname = body.get("nickname");
        if (WechatSopce.USER_INFO.equals(sopce)) {
            body = getUserInfo(accessToken, openId);
            unionId = body.get("unionid");
            headimgurl = body.get("headimgurl");
            nickname = body.get("nickname");
        }

        UserWechat userWechat = new UserWechat();
        userWechat.setUserId(userId);
        userWechat.setOpenId(openId);
        userWechat.setUnionId(unionId == null ? null : String.valueOf(unionId));
        userWechat.setHeadImg(headimgurl == null ? null : String.valueOf(headimgurl));
        userWechat.setNikeName(nickname == null ? null : String.valueOf(nickname));
        userWechat.setManualBind(WechatSopce.USER_INFO.equals(sopce));

        return userWechat;
    }

    @Override
    public void bindUser(UserWechat userWechat) {
        //查询openID 是否绑定
        UserWechat openIdDO = userWechatService.queryByOpenIdInCache(userWechat.getOpenId());
        if (!userWechat.getManualBind() && openIdDO != null && openIdDO.getManualBind()) {
            //当前操作为自动绑定，但openId已被手动绑定
            log.info("用户ID[{}],openId[{}],openId已被手动绑定,无法自动更新绑定关系。");
            return;
        }
        //查询该用户是否绑定
        UserWechat userIdDO = userWechatService.queryByUserIdInCache(userWechat.getUserId());
        if (!userWechat.getManualBind() && userIdDO != null && userIdDO.getManualBind()) {
            //当前操作为自动绑定，但userId已被手动绑定
            log.info("用户ID[{}],openId[{}],openId已被手动绑定,无法自动更新绑定关系。");
            return;
        }
        if (openIdDO != null && userIdDO != null && openIdDO.getId().equals(userIdDO.getId())) {
            //openId 和 userid 绑定则更新
            userWechat.setId(openIdDO.getId());
            userWechatService.updateUserWechat(userWechat);
            return;
        }
        if (openIdDO != null && userIdDO == null) {
            //openid已绑定，用户未绑定
            userWechat.setId(openIdDO.getId());
            userWechatService.updateUserWechat(userWechat);
            return;
        }
        if (userIdDO != null && openIdDO == null) {
            //用户已绑定，openid未绑定
            userWechat.setId(userIdDO.getId());
            userWechatService.updateUserWechat(userWechat);
            return;
        }

        //插入操作，会删除openId 和 userId 分别绑定的关系
        userWechatService.inserUserWechat(userWechat);
    }

    @Override
    public void unbind(Long userId) {

        UserWechat userWechat = userWechatService.queryByUserIdInCache(userId);
        state(userWechat != null, "暂未绑定");

        UserWechat delDO = new UserWechat();
        delDO.setUserId(userId);
        delDO.setOpenId(userWechat.getOpenId());

        userWechatService.deleteUserWechat(delDO);
    }

    @Override
    public Result sendTemplateMessage(WechatTemplateDTO wechatTemplateDTO) {

        String json = JsonObjectMapper.toJson(wechatTemplateDTO);
        log.info("发送模版请求参数: {}", json);

        //记录日志
        WechatTemplateLog requestLog = new WechatTemplateLog();
        requestLog.setOpenId(wechatTemplateDTO.getOpneId());
        requestLog.setRequestData(json);
        requestLog.setCreateTime(new Date());
        wechatTemplateLogDao.insert(requestLog);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);

        ResponseEntity<String> responseEntity = restTemplate.postForEntity(templateSendUrl, httpEntity, String.class, wechatTemplateDTO.getAccessToken());
        if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
            return Result.fail("发送模版请求失败");
        }

        log.info("给用户【{}】发送微信消息模版回调报文 responseData： {}", wechatTemplateDTO.getOpneId(), responseEntity.getBody());

        //保存回调记录
        WechatTemplateLog responseLog = new WechatTemplateLog();
        responseLog.setId(requestLog.getId());
        responseLog.setResponseData(responseEntity.getBody());
        responseLog.setUpdateTime(new Date());
        mongoDao.update(responseLog);

        Map<String, Object> result = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        Integer errcode = (Integer) result.get("errcode");
        if (!Integer.valueOf(0).equals(errcode)) {
            return Result.fail("发送模版失败");
        }

        return Result.success();
    }

    @Override
    public Result autoLogin(String code) {
    	//获取用户access_token、openid
        Map<String, Object> body = getUserAccessToken(code);
        String openid = body.get("openid").toString();
        return userService.loginByWecaht(openid);
    }

    /**
     * 根据授权回调的code获取用户的access_token，同时也会返回openid
     *
     * @param code
     * @return
     */
    @Override
    public Map<String, Object> getUserAccessToken(String code) {
        Map<String, String> params = new HashMap<>();
        params.put("appid", appId);
        params.put("secret", secret);
        params.put("code", code);
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(oauth2AccessTokenUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求微信ACCESSTOKEN失败");

        log.info("微信授权回调: {}", responseEntity.getBody());

        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);

        Object errcode = body.get("errcode");
        state(errcode == null, "获取微信ACCESSTOKEN失败");
        return body;
    }

    /**
     * 根据用户的access_token、openId获取用户信息
     *
     * @param accessToken
     * @param openId
     * @return
     */
    @Override
    public Map<String, Object> getUserInfo(String accessToken, String openId) {
        Map<String, String> params = new HashMap<>();
        params.put("access_token", accessToken);
        params.put("openid", openId);
        //缓存
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(userInfoUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求微信USERINFO失败");

        log.info("微信用户信息查询回调: {}", responseEntity.getBody());

        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);

        Object errcode = body.get("errcode");
        state(errcode == null, "获取微信USERINFO失败");
        return body;
    }

}
